FOR · Laboratori

Scheda rapida Python-MIP

L'API usata nei laboratori, gli schemi di codice ricorrenti e il glossario

Questa scheda raccoglie in un unico posto tutto ciò che di Python-MIP serve nei laboratori e nella parte di programmazione dell’esame. Non introduce nulla di nuovo: ogni riga rimanda al capitolo in cui il costrutto è stato usato per la prima volta.

1. L’API essenziale#

Che cosa Codice Note
importare import mip su Colab prima !pip install mip
creare il modello m = mip.Model() opzionale sense=mip.MAXIMIZE / mip.MINIMIZE
variabile continua 0\ge 0 x = m.add_var() limiti predefiniti lb=0, ub=INF
variabile intera / binaria m.add_var(var_type=mip.INTEGER) / mip.BINARY mip.CONTINUOUS è il default
altri argomenti di add_var lb=, ub=, name= name rende leggibile la stampa dei vincoli
vincolo m.add_constr(espr <= b) oppure m += espr <= b <=, >=, == (mai =)
somma di espressioni mip.xsum(c[i]*x[i] for i in range(n)) accetta un generatore o una lista
obiettivo m.objective = mip.maximize(espr) / mip.minimize(espr) uno solo, si assegna
risolvere status = m.optimize() restituisce un OptimizationStatus
valore di una variabile x.x solo dopo optimize()
valore dell’obiettivo m.objective_value
lista dei vincoli, duali m.constrs, con.pi il duale è lo shadow price del vincolo
lista delle variabili, costi ridotti m.vars, v.rc m.vars[2] è la terza variabile creata
silenziare il solver m.verbose = 0 utile nei cicli con molte risoluzioni

Lo stato restituito da optimize() va controllato quando il modello potrebbe essere inammissibile: mip.OptimizationStatus.OPTIMAL indica che la soluzione è ottima, INFEASIBLE che non esistono soluzioni, UNBOUNDED che l’obiettivo può crescere senza limite. Leggere x.x su un modello inammissibile restituisce None.

2. Gli schemi che ricorrono#

Variabili indicizzate da un insieme di numeri consecutivi, in una lista (cap. 1):

x = [m.add_var(var_type=mip.INTEGER) for i in range(n)]

Variabili indicizzate da coppie, o da un insieme qualunque, in un dizionario (cap. 2):

f = {(i,j): m.add_var() for (i,j) in A}              # archi
x = {p: m.add_var() for p in P}                       # un sottoinsieme di indici

Una famiglia di vincoli, «per ogni jj»: un ciclo for con dentro una xsum sull’altro indice.

for j in range(n_component):
    m.add_constr(mip.xsum(A[j][i]*x[i] for i in range(n_model)) <= b[j])

Somme filtrate, per esempio il flusso uscente e quello entrante in un nodo:

mip.xsum(f[i,j] for j in V if (i,j) in A) - mip.xsum(f[j,i] for j in V if (j,i) in A) == b[i]

Lettura di una soluzione binaria con una soglia, mai con == 1:

sol = [(i,j) for (i,j) in E if x[i,j].x > 0.5]

Una funzione che risolve il modello per dati diversi, che restituisce modello, soluzione e obiettivo (cap. 3):

def solve(A, b, c):
    m = mip.Model()
    x = [m.add_var() for j in range(len(c))]
    for i in range(len(b)):
        m.add_constr(mip.xsum(A[i][j]*x[j] for j in range(len(c))) <= b[i])
    m.objective = mip.maximize(mip.xsum(c[j]*x[j] for j in range(len(c))))
    m.optimize()
    return m, [v.x for v in x], m.objective_value

Aggiungere vincoli a posteriori e riottimizzare (tagli di subtour elimination, cap. 2):

while esiste_un_vincolo_violato(sol):
    m.add_constr(...)
    m.optimize()

Dati che vanno copiati prima di essere modificati: b3 = b[:] crea una copia; b3 = b crea solo un secondo nome per la stessa lista, e modificare b3 modifica anche b.

3. La scaletta di modellazione#

Da ripetere identica per ogni problema, prima di scrivere una riga di codice.

  1. Insiemi: di che cosa si parla (prodotti, risorse, nodi, archi, periodi). Ogni insieme diventa un range o una lista.
  2. Parametri: tutti i numeri del testo, e solo quelli, con l’indice giusto (cic_i, ajia_{ji}, bjb_j). Lettere iniziali dell’alfabeto.
  3. Variabili decisionali: che cosa si decide, con che indice e di che natura (continua, intera, binaria). Lettere finali dell’alfabeto.
  4. Obiettivo: una sola espressione lineare, da massimizzare o minimizzare.
  5. Vincoli: una famiglia per ogni «per ogni» del testo; per ognuna, quale indice è fissato e su quale corre la somma.
  6. Traduzione: dati, Model(), variabili, vincoli, obiettivo, optimize(), stampa. Poi verifica a mano che la soluzione rispetti i vincoli.

4. Errori tipici#

Sintomo Causa probabile Rimedio
la soluzione ha valori frazionari inattesi manca var_type mip.INTEGER o mip.BINARY
print(x) stampa un oggetto manca .x x.x, m.objective_value
KeyError su x[i,j] la coppia non è nell’insieme degli archi, o è nell’ordine sbagliato filtrare con if (i,j) in A, rispettare i<ji<j
la soluzione non cambia dopo una modifica cella non rilanciata, o add_constr che aggiunge invece di sostituire modificare la cella originale, riavviare il kernel
SyntaxError su un vincolo di uguaglianza = al posto di == ==
soluzione ottima con subtour mancano i tagli separare e aggiungere i vincoli violati
status non ottimale, x.x è None modello inammissibile (dati, nodo isolato) controllare i dati e il grafo
il ciclo di tagli non termina termine di destra del taglio troppo largo usare len(cycle) - 1

5. Glossario#

Termine Significato
LP, ILP, MIP programmazione lineare; con variabili intere; con variabili miste
variabile decisionale quantità che il modello sceglie; continua, intera o binaria
parametro dato numerico del problema, fissato prima della risoluzione
forma standard max{cx:Axb, x0}\max\{c^\top x : Ax \le b,\ x \ge 0\}
rilassamento continuo lo stesso modello con le variabili intere rese continue; ottimo non peggiore
knapsack scegliere oggetti di valore massimo entro una capacità; variabili binarie
flusso, conservazione quantità sugli archi; in ogni nodo uscente meno entrante uguale a bib_i
cut set δ(S)\delta(S) lati con un solo estremo in SS
subtour, subtour elimination ciclo parziale; vincolo che lo esclude, CxC1\sum_{C} x \le \lvert C \rvert - 1
separazione, taglio trovare un vincolo violato e aggiungerlo al modello
vincolo attivo soddisfatto con uguaglianza all’ottimo
variabile duale, shadow price valore marginale di un’unità in più del termine di destra
costo ridotto cjiaijyic_j - \sum_i a_{ij} y_i^*; quanto deve cambiare cjc_j perché xjx_j entri in soluzione
pricing, generazione di colonne aggiungere le colonne a costo ridotto positivo finché non ne restano
solver il programma (CBC, per Python-MIP) che risolve il modello con simplesso e branch and bound

Sintesi trasversale dei capitoli 1, 2 e 3: da tenere aperta mentre si scrive un modello.